// number of seconds a reel should spin for
final float SPIN_DURATION = 5.0;
// initial reel velocity in pixels per second
final float INITIAL_VELOCITY = 100.0;
final int MIN_SPIN = 1;
final int MAX_SPIN = 4;
final float VICTORY_CHANCE = .5;
final int VICTORY_WAIT_MILLIS = 4000;
final int LOSS_WAIT_MILLIS = 7000;

// Represents a simple slot machine
class SlotMachine extends WallMode {
  // most of the work is encapsulated in each reel
  Reel[] reels;
  int symbolCount;
  int symbolHeightPx;
  SlotSymbol[] symbols;
  boolean changeMode;
  int newModeId;
  boolean newSpin = false;
  int victoryTime, lossTime;
  PImage output = createImage(48, 48, RGB);
  Marquee winText;
  Marquee pullText;

  SlotMachine(int reels, int symbolCount, int symbolHeightPx) {
    this.symbolCount = symbolCount;
    this.symbolHeightPx = symbolHeightPx;
    this.reels = new Reel[reels];
    pullText = new Marquee();
    pullText.add(new TextInfo("pull", 5, 15, 255, 0, 0));
    pullText.add(new TextInfo("the", 13, 33, 0, 255, 0));
    pullText.add(new TextInfo("lever", 0, 44, 0, 0, 255));

    winText = new Marquee();
    winText.add(new TextInfo("WIN!", 13, 15, 255, 0, 0));
    winText.add(new TextInfo("WIN!", 13, 33, 255, 0, 0));
    winText.add(new TextInfo("WIN!", 13, 44, 255, 0, 0));
  }

  void loadSymbols() {
    symbols = new SlotSymbol[symbolCount];
    PImage reelImage = loadImage("reel1.png");
    for (int i = 0; i < symbolCount; i++) { 
      symbols[i] = new SlotSymbol(i, reelImage.get(0, i*16, 16, i*16+16));
    }
  }

  void setup() {
    loadSymbols();
    for (int i = 0; i < reels.length; i++)
      this.reels[i] = new Reel(this, (int)random(0, symbolCount));
    this.spinIndefinite();
  }

  void draw() { 
    this.update();
    Reel[] reels = this.getReels();
    for (int i = 0; i < reels.length; i++) {
      updateWindow(output, reels[i].pixelPosition(), i*16, reels[i].getReelWindow());
    }
    image(output, 0, 0);
  }

  void modeSelected() {
    changeMode = false;
    newSpin = false;
    this.spinIndefinite();
    newModeId = 0;
  }

  void keyPressed() {
    if (key == 's')
      this.spin();
    if (key == '\n')
      this.spin();
  }

  //slot machine functions
  void updateWindow(PImage dst, int srcOffsetY, int dstOffsetX, PImage src) {
    dst.loadPixels();
    if ((srcOffsetY % 160) < 112) {
      dst.set(dstOffsetX, 0, src.get(0, srcOffsetY % 160, 16, 48));
    } 
    else { 
      //wrap around
      //top part keeps reading until the bottom getting smaller and smaller
      dst.set(dstOffsetX, 0, src.get(0, srcOffsetY % 160, 16, 160 - srcOffsetY % 160));
      //bottom part starts reading at the top getting bigger and bigger and adding it to the bottom
      dst.set(dstOffsetX, 160 - srcOffsetY % 160, src.get(0, 0, 16, srcOffsetY % 160 - 112));
      //dst.set(0,(count % 160)-112,reel1.get(0, 160 - (count % 160), 16, 160 - count % 160));
    }
    dst.updatePixels();
  }

  int getImageHeight() {
    return symbolHeightPx * symbolCount;
  }

  int getSymbolCount() {
    return symbolCount;
  }

  int getSymbolHeightPx() {
    return symbolHeightPx;
  }

  SlotSymbol getSymbol(int symbolNumber) {
    return symbols[symbolNumber];
  }

  // spins the reels indefinitely!
  void spinIndefinite() {
    for (int i = 0; i < reels.length; i++)
      reels[i].spinIndefinite((int)random(INITIAL_VELOCITY/3, INITIAL_VELOCITY));
  }

  // spins the reels
  void spin() {
    newSpin = true;
    // determine if this is a winning scenario, if so, fix the result
    if (random(0, 1) < VICTORY_CHANCE) {
      int winningSymbolID = (int)random(0, symbolCount);
      for (int i = 0; i < reels.length; i++) {
        reels[i].spin((int)random(MIN_SPIN, MAX_SPIN)*symbolCount + reels[i].distanceToSymbol(winningSymbolID));
      }
    }
    else {     
      for (int i = 0; i < reels.length; i++)
        reels[i].spin((int)random(MIN_SPIN * symbolCount, MAX_SPIN * symbolCount));
    }
  }

  // updates the reels
  void update() {
    int notSpinningCount = 0;
    for (int i = 0; i < reels.length; i++)
      if (reels[i].getIsSpinning())
        reels[i].update();
      else 
        notSpinningCount++;
    if (newSpin && notSpinningCount == 3)
      checkVictory();
  }

  boolean checkVictory() {
    int symbolId = this.reels[0].getCurrentSymbolId();
    for (int i = 0; i < this.reels.length; i++)
      if (this.reels[i].getCurrentSymbolId() != symbolId)
      {
        lossTime = millis();
        changeMode = true;
        newSpin = true;
        
        return false;
      }
    victoryTime = millis();
    changeMode = true;
    newModeId = int(random(1, 9));
    newSpin = false;
    return true;
  }

  boolean shouldChangeMode() {
    if (changeMode &&  (millis() - victoryTime) > VICTORY_WAIT_MILLIS)
      return changeMode;
    if (changeMode && (millis() - lossTime) > LOSS_WAIT_MILLIS)
      return changeMode;    
    else return false;
  }

  int getNewModeIdentifier() {
    return newModeId;
  }

  Reel[] getReels() { 
    return reels;
  }
}

class SlotSymbol {
  SlotSymbol(int id, PImage img) {
    this.id = id;
    this.img = img;
  } 
  int id;
  PImage img;  
  int getId() { 
    return id;
  }
  PImage getImg() { 
    return img;
  }
}

